package com.zhx.common.common.util;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zhx.common.common.domain.vo.req.CursorPageBaseReq;
import com.zhx.common.common.domain.vo.resp.CursorPageBaseResp;
import org.springframework.data.redis.core.ZSetOperations;
import utils.RedisUtils;

import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Description: 游标分页工具类
 */
public class CursorUtils {

    /**
     * 根据Redis的有序集合获取分页数据。
     *
     * @param cursorPageBaseReq 分页请求对象，包含游标和每页大小等信息。
     * @param redisKey Redis中有序集合的键名。
     * @param typeConvert 将Redis中存储的String类型转换为泛型T的转换函数。
     * @return CursorPageBaseResp 分页响应对象，包含下一个游标、是否为最后一页以及数据列表。
     * @param <T> 泛型参数，表示数据类型。
     * ZADD [KEY]  [SOCRE] [VALUE]
     * 添加一个成员
     * ZADD 成员列表key 上线时间  UID
     */
    public static <T> CursorPageBaseResp<Pair<T, Double>> getCursorPageByRedis(CursorPageBaseReq cursorPageBaseReq, String redisKey, Function<String, T> typeConvert) {
        Set<ZSetOperations.TypedTuple<String>> typedTuples;
        if (StrUtil.isBlank(cursorPageBaseReq.getCursor())) {//第一次请求，从头开始取数据
            typedTuples = RedisUtils.zReverseRangeWithScores(redisKey, cursorPageBaseReq.getPageSize());
        } else {
            // 根据游标和页面大小，获取指定范围的数据
            typedTuples = RedisUtils.zReverseRangeByScoreWithScores(redisKey, Double.parseDouble(cursorPageBaseReq.getCursor()), cursorPageBaseReq.getPageSize());
        }

        // 将获取到的数据转换为泛型T和分数的Pair列表，并按分数降序排序
        List<Pair<T, Double>> result = typedTuples
                .stream()
                .map(t -> Pair.of(typeConvert.apply(t.getValue()), t.getScore()))
                .sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue()))
                .collect(Collectors.toList());

        // 计算下一个游标，如果结果集小于页面大小，则说明是最后一页
        String cursor = Optional.ofNullable(CollectionUtil.getLast(result))
                .map(Pair::getValue)
                .map(String::valueOf)
                .orElse(null);
        Boolean isLast = result.size() != cursorPageBaseReq.getPageSize();

        // 构建并返回分页响应对象
        return new CursorPageBaseResp<>(cursor, isLast, result);
    }

    /**
     * 通过MySQL获取游标分页数据
     * select * from table where uid = ? and id>100 order by id desc limit 0,10
     * 这里 uid = ?是额外条件，id>100是游标条件，id是游标字段类型
     * @param mapper 实体类Mapper接口
     * @param request 游标分页请求体
     * @param initWrapper 额外的查询条件 (uid = ?)
     * @param cursorColumn 游标字段类型 (id,data...)
     * @param <T> 泛型类型
     * @return 游标分页响应体
     */
    public static <T> CursorPageBaseResp<T> getCursorPageByMysql(IService<T> mapper, CursorPageBaseReq request, Consumer<LambdaQueryWrapper<T>> initWrapper, SFunction<T, ?> cursorColumn) {
        //游标字段类型 是date类型时，需要特殊处理
        Class<?> cursorType = LambdaUtils.getReturnType(cursorColumn);
        LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
        //额外条件
        initWrapper.accept(wrapper);
        //游标条件
        if (StrUtil.isNotBlank(request.getCursor())) {
            wrapper.lt(cursorColumn, parseCursor(request.getCursor(), cursorType));
        }
        //游标方向 order by id desc 因为消息都是往上翻得
        wrapper.orderByDesc(cursorColumn);

        Page<T> page = mapper.page(request.plusPage(), wrapper);
        //计算游标位置
        String cursor = Optional.ofNullable(CollectionUtil.getLast(page.getRecords()))
                .map(cursorColumn)
                .map(CursorUtils::toCursor)
                .orElse(null);
        //判断是否最后一页
        Boolean isLast = page.getRecords().size() != request.getPageSize();
        return new CursorPageBaseResp<>(cursor, isLast, page.getRecords());
    }

    /**
     * 将对象转换为字符串表示形式。
     * 如果对象是Date类型，则转换为表示时间戳的字符串；
     * 否则，直接调用对象的toString方法进行转换。
     *
     * @param o 待转换的对象。
     * @return 表示对象的字符串。
     */
    private static String toCursor(Object o) {
        if (o instanceof Date) {
            // 如果对象是Date类型，转换为时间戳字符串
            return String.valueOf(((Date) o).getTime());
        } else {
            // 对象不是Date类型，直接调用toString方法转换
            return o.toString();
        }
    }

    /**
     * 解析游标字符串为相应的对象。
     *
     * @param cursor 游标字符串，可能是日期的字符串表示或其它类型的字符串。
     * @param cursorClass 游标对应对象的类类型，用于指示游标字符串应转换成的类型。
     * @return 根据游标类类型，将字符串转换为相应的对象。如果类类型是Date，则转换为对应的Date对象；否则，直接返回字符串本身。
     */
    private static Object parseCursor(String cursor, Class<?> cursorClass) {
        // 如果游标类类型是Date，则将字符串表示的日期转换为Date对象
        if (Date.class.isAssignableFrom(cursorClass)) {
            return new Date(Long.parseLong(cursor));
        } else {
            // 否则，直接返回游标字符串本身
            return cursor;
        }
    }
}
